using System;

using Org.BouncyCastle.Crypto.Parameters;

namespace Org.BouncyCastle.Crypto.Engines
{
	/**
	* An RC6 engine.
	*/
	public class RC6Engine
		: IBlockCipher
	{
		private static readonly int wordSize = 32;
		private static readonly int bytesPerWord = wordSize / 8;

		/*
		* the number of rounds to perform
		*/
		private static readonly int _noRounds = 20;

		/*
		* the expanded key array of size 2*(rounds + 1)
		*/
		private int [] _S;

		/*
		* our "magic constants" for wordSize 32
		*
		* Pw = Odd((e-2) * 2^wordsize)
		* Qw = Odd((o-2) * 2^wordsize)
		*
		* where e is the base of natural logarithms (2.718281828...)
		* and o is the golden ratio (1.61803398...)
		*/
		private static readonly int    P32 = unchecked((int) 0xb7e15163);
		private static readonly int    Q32 = unchecked((int) 0x9e3779b9);

		private static readonly int    LGW = 5;        // log2(32)

		private bool forEncryption;

		/**
		* Create an instance of the RC6 encryption algorithm
		* and set some defaults
		*/
		public RC6Engine()
		{
//            _S            = null;
		}

		public string AlgorithmName
		{
			get { return "RC6"; }
		}

		public bool IsPartialBlockOkay
		{
			get { return false; }
		}

		public int GetBlockSize()
		{
			return 4 * bytesPerWord;
		}

		/**
		* initialise a RC5-32 cipher.
		*
		* @param forEncryption whether or not we are for encryption.
		* @param parameters the parameters required to set up the cipher.
		* @exception ArgumentException if the parameters argument is
		* inappropriate.
		*/
		public void Init(
			bool				forEncryption,
			ICipherParameters	parameters)
		{
			if (!(parameters is KeyParameter))
				throw new ArgumentException("invalid parameter passed to RC6 init - " + parameters.GetType().ToString());

			this.forEncryption = forEncryption;

			KeyParameter p = (KeyParameter)parameters;
			SetKey(p.GetKey());
		}

		public int ProcessBlock(
			byte[]	input,
			int		inOff,
			byte[]	output,
			int		outOff)
		{
			int blockSize = GetBlockSize();
			if (_S == null)
				throw new InvalidOperationException("RC6 engine not initialised");
			if ((inOff + blockSize) > input.Length)
				throw new DataLengthException("input buffer too short");
			if ((outOff + blockSize) > output.Length)
				throw new DataLengthException("output buffer too short");

			return (forEncryption)
				?	EncryptBlock(input, inOff, output, outOff)
				:	DecryptBlock(input, inOff, output, outOff);
		}

		public void Reset()
		{
		}

		/**
		* Re-key the cipher.
		*
		* @param inKey the key to be used
		*/
		private void SetKey(
			byte[] key)
		{
			//
			// KEY EXPANSION:
			//
			// There are 3 phases to the key expansion.
			//
			// Phase 1:
			//   Copy the secret key K[0...b-1] into an array L[0..c-1] of
			//   c = ceil(b/u), where u = wordSize/8 in little-endian order.
			//   In other words, we fill up L using u consecutive key bytes
			//   of K. Any unfilled byte positions in L are zeroed. In the
			//   case that b = c = 0, set c = 1 and L[0] = 0.
			//
			// compute number of dwords
			int c = (key.Length + (bytesPerWord - 1)) / bytesPerWord;
			if (c == 0)
			{
				c = 1;
			}
			int[]   L = new int[(key.Length + bytesPerWord - 1) / bytesPerWord];

			// load all key bytes into array of key dwords
			for (int i = key.Length - 1; i >= 0; i--)
			{
				L[i / bytesPerWord] = (L[i / bytesPerWord] << 8) + (key[i] & 0xff);
			}

			//
			// Phase 2:
			//   Key schedule is placed in a array of 2+2*ROUNDS+2 = 44 dwords.
			//   Initialize S to a particular fixed pseudo-random bit pattern
			//   using an arithmetic progression modulo 2^wordsize determined
			//   by the magic numbers, Pw & Qw.
			//
			_S            = new int[2+2*_noRounds+2];

			_S[0] = P32;
			for (int i=1; i < _S.Length; i++)
			{
				_S[i] = (_S[i-1] + Q32);
			}

			//
			// Phase 3:
			//   Mix in the user's secret key in 3 passes over the arrays S & L.
			//   The max of the arrays sizes is used as the loop control
			//
			int iter;

			if (L.Length > _S.Length)
			{
				iter = 3 * L.Length;
			}
			else
			{
				iter = 3 * _S.Length;
			}

			int A = 0;
			int B = 0;
			int ii = 0, jj = 0;

			for (int k = 0; k < iter; k++)
			{
				A = _S[ii] = RotateLeft(_S[ii] + A + B, 3);
				B =  L[jj] = RotateLeft( L[jj] + A + B, A+B);
				ii = (ii+1) % _S.Length;
				jj = (jj+1) %  L.Length;
			}
		}

		private int EncryptBlock(
			byte[]  input,
			int     inOff,
			byte[]  outBytes,
			int     outOff)
		{
			// load A,B,C and D registers from in.
			int A = BytesToWord(input, inOff);
			int B = BytesToWord(input, inOff + bytesPerWord);
			int C = BytesToWord(input, inOff + bytesPerWord*2);
			int D = BytesToWord(input, inOff + bytesPerWord*3);

			// Do pseudo-round #0: pre-whitening of B and D
			B += _S[0];
			D += _S[1];

			// perform round #1,#2 ... #ROUNDS of encryption
			for (int i = 1; i <= _noRounds; i++)
			{
				int t = 0,u = 0;

				t = B*(2*B+1);
				t = RotateLeft(t,5);

				u = D*(2*D+1);
				u = RotateLeft(u,5);

				A ^= t;
				A = RotateLeft(A,u);
				A += _S[2*i];

				C ^= u;
				C = RotateLeft(C,t);
				C += _S[2*i+1];

				int temp = A;
				A = B;
				B = C;
				C = D;
				D = temp;
			}
			// do pseudo-round #(ROUNDS+1) : post-whitening of A and C
			A += _S[2*_noRounds+2];
			C += _S[2*_noRounds+3];

			// store A, B, C and D registers to out
			WordToBytes(A, outBytes, outOff);
			WordToBytes(B, outBytes, outOff + bytesPerWord);
			WordToBytes(C, outBytes, outOff + bytesPerWord*2);
			WordToBytes(D, outBytes, outOff + bytesPerWord*3);

			return 4 * bytesPerWord;
		}

		private int DecryptBlock(
			byte[]  input,
			int     inOff,
			byte[]  outBytes,
			int     outOff)
		{
			// load A,B,C and D registers from out.
			int A = BytesToWord(input, inOff);
			int B = BytesToWord(input, inOff + bytesPerWord);
			int C = BytesToWord(input, inOff + bytesPerWord*2);
			int D = BytesToWord(input, inOff + bytesPerWord*3);

			// Undo pseudo-round #(ROUNDS+1) : post whitening of A and C
			C -= _S[2*_noRounds+3];
			A -= _S[2*_noRounds+2];

			// Undo round #ROUNDS, .., #2,#1 of encryption
			for (int i = _noRounds; i >= 1; i--)
			{
				int t=0,u = 0;

				int temp = D;
				D = C;
				C = B;
				B = A;
				A = temp;

				t = B*(2*B+1);
				t = RotateLeft(t, LGW);

				u = D*(2*D+1);
				u = RotateLeft(u, LGW);

				C -= _S[2*i+1];
				C = RotateRight(C,t);
				C ^= u;

				A -= _S[2*i];
				A = RotateRight(A,u);
				A ^= t;

			}
			// Undo pseudo-round #0: pre-whitening of B and D
			D -= _S[1];
			B -= _S[0];

			WordToBytes(A, outBytes, outOff);
			WordToBytes(B, outBytes, outOff + bytesPerWord);
			WordToBytes(C, outBytes, outOff + bytesPerWord*2);
			WordToBytes(D, outBytes, outOff + bytesPerWord*3);

			return 4 * bytesPerWord;
		}


		//////////////////////////////////////////////////////////////
		//
		// PRIVATE Helper Methods
		//
		//////////////////////////////////////////////////////////////

		/**
		* Perform a left "spin" of the word. The rotation of the given
		* word <em>x</em> is rotated left by <em>y</em> bits.
		* Only the <em>lg(wordSize)</em> low-order bits of <em>y</em>
		* are used to determine the rotation amount. Here it is
		* assumed that the wordsize used is a power of 2.
		*
		* @param x word to rotate
		* @param y number of bits to rotate % wordSize
		*/
		private int RotateLeft(int x, int y)
		{
			return ((int)((uint)(x << (y & (wordSize-1)))
				| ((uint) x >> (wordSize - (y & (wordSize-1))))));
		}

		/**
		* Perform a right "spin" of the word. The rotation of the given
		* word <em>x</em> is rotated left by <em>y</em> bits.
		* Only the <em>lg(wordSize)</em> low-order bits of <em>y</em>
		* are used to determine the rotation amount. Here it is
		* assumed that the wordsize used is a power of 2.
		*
		* @param x word to rotate
		* @param y number of bits to rotate % wordSize
		*/
		private int RotateRight(int x, int y) 
		{
			return ((int)(((uint) x >> (y & (wordSize-1)))
				| (uint)(x << (wordSize - (y & (wordSize-1))))));
		}

		private int BytesToWord(
			byte[]	src,
			int		srcOff)
		{
			int word = 0;

			for (int i = bytesPerWord - 1; i >= 0; i--)
			{
				word = (word << 8) + (src[i + srcOff] & 0xff);
			}

			return word;
		}

		private void WordToBytes(
			int		word,
			byte[]	dst,
			int		dstOff)
		{
			for (int i = 0; i < bytesPerWord; i++)
			{
				dst[i + dstOff] = (byte)word;
				word = (int) ((uint) word >> 8);
			}
		}
	}

}
